
#include "terrain.h"
#include "3d.h"

using namespace WtfGraphics;
using namespace WtfEngine;

HeightMapShader::HeightMapShader():
	GLSLProgram()
{
	GLSLShader::tRef	rFrag(new GLSLShader());
	GLSLShader::tRef	rVert(new GLSLShader());

	rFrag->Load("src/shaders/simple_tex.frag");
	rVert->Load("src/shaders/terrain_interpolation.vert");

	this->Load(rFrag, rVert);
};

void HeightMapShader::GetLocations() {
	mHeightMapLocation = glGetUniformLocationARB(hndProgram, "gHeightMap");
	mScaleLocation = glGetUniformLocationARB(hndProgram, "gScale");
	mTextureLocation = glGetUniformLocationARB(hndProgram, "gTexture");
};

void HeightMapShader::PassOther() {
	// Pass location of height map texture
	glUniform1iARB(mHeightMapLocation, 1);
	checkGLError("passing height map texture handle");
	
	glUniform1iARB(mTextureLocation, 0);
	checkGLError("passing texture handle");
};

void HeightMapShader::setScale(const tVector& scale) {
	// Pass scale factor
	glUniform3fARB(mScaleLocation, scale[0], scale[1], scale[2]);
	checkGLError("passing scale factor");
};


/**** TerrainHeightMap ****/

/**
 * Convert a 32 bit per channel floating point pixel to a height. Colour values are expected to
 * range from 0 - 1.
 *	Height = Red + (Green * 256) + (Blue * 256 * 256)
 **/
inline tScalar __FloatPixelToHeight(const PixelType::tRGBA32f::tPixelType& px) {
	return (px[0] + (px[1] * 256) + (px[2] * 256 * 256));
};
inline PixelType::tRGBA32f::tPixelType __HeightToFloatPixel(tScalar h) {
	PixelType::tRGBA32f::tPixelType px;
	px[2] = h / (256.0 * 256.0);
	h -= (px[2] * 256.0 * 256.0);
	px[1] = (h / 256.0);
	h -= (px[1] * 256.0);
	px[0] = h;

	return px;
};

/**
 * Convert an 8 bit per channel pixel to a height. Colour values are expected to
 * range from 0 - 255.
 *	Height = (Red / 256) + Green + (Blue * 256)
 **/
inline tScalar __BytePixelToHeight(const PixelType::tRGBA8::tPixelType& px) {
	return (((tScalar)px[0]) / 256.0) + (tScalar)px[1] + (((tScalar)px[2]) * 256.0);
};
inline PixelType::tRGBA8::tPixelType __HeightToBytePixel(tScalar h) {
	PixelType::tRGBA8::tPixelType px;
	px[2] = (unsigned char)(h / 256.0);
	h -= (((tScalar)px[2]) * 256.0);
	px[1] = (unsigned char) h;
	h -= px[1];
	px[0] = (unsigned char)(h * 256.0);

	return px;
};


/**
 * Given a floating point array of heights, and coordinates, this generates
 * a quad from (i, j) to (i + 1, j + 1) on the XZ plane, with appropriate
 * heights and vertex normals.
 **/
inline void __GenerateQuad(tScalar * pHeightMap, int i, int j, int xdim,
						   tVector& v1, tVector& v2, tVector& v3, tVector& v4,
						   tVector& n1, tVector& n2, tVector& n3, tVector& n4)
{
#	define CALC_VERTEX(i, j)	tVector(i, pHeightMap[(i) + (xdim * (j))], j)

	v1 = CALC_VERTEX(i, j);
	v2 = CALC_VERTEX(i + 1, j);
	v3 = CALC_VERTEX(i + 1, j + 1);
	v4 = CALC_VERTEX(i, j + 1);

#	define CALC_NORMAL(i, j) \
		(CALC_VERTEX(i - 1, j) - CALC_VERTEX(i + 1, j)).cross	\
			(CALC_VERTEX(i, j - 1) - CALC_VERTEX(i, j + 1)).normalize();

	// Calculate normal vectors
	n1 = CALC_NORMAL(i, j);
	n2 = CALC_NORMAL((i + 1), j);
	n3 = CALC_NORMAL((i + 1), (j + 1));
	n4 = CALC_NORMAL(i, (j + 1));
};


void TerrainHeightMap::Create(const tVector& vScale, const WtfEngine::Image::tRef& rHeightMap, tDataType dataType) {
	
	Destroy();

	if(dataType == HTMAP_DEFAULT) dataType = GetDefaultType();

	mDataType = dataType;
	mvDim = rHeightMap->getSize();
	mvScale = vScale;
	
	switch(dataType) {
		case HTMAP_TEXTURE:
			mrHeightMap = new Texture(getName());
			mrHeightMap->Create(rHeightMap->getPixels(), mvDim);

			break;

		case HTMAP_FLOATV:
			{
				mrvfHeightMap = new NonCopyBuffer<tScalar>(mvDim[0] * mvDim[1]);

				tScalar * pHeightMap(mrvfHeightMap->getData());
				WtfEngine::Image::tPixelBuffer::tRef rPixels(rHeightMap->getPixels());

				for(int i = 0; i < mvDim[0] * mvDim[1]; i++) {
					pHeightMap[i] = __BytePixelToHeight((*rPixels)[i]) * mvScale[1];
				};

				// Pre-scaled
				mvScale[1] = 1.0;
			};

			break;

		case HTMAP_MESH:
			{
				tScalar * pHeightMap(new tScalar[mvDim[0] * mvDim[1]]);
				WtfEngine::Image::tPixelBuffer::tRef rPixels(rHeightMap->getPixels());

				for(int i = 0; i < mvDim[0] * mvDim[1]; i++) {
					pHeightMap[i] = __BytePixelToHeight((*rPixels)[i]) * mvScale[1];
				};

				// Pre-scaled
				mvScale[1] = 1.0;
				
				// Create the mesh
				mrMesh = new Mesh(getName());

				tVector					vv[4], vn[4];
				tScalar					h;
				std::vector<Triangle>	vNormals;

#			define __SCALE_VERTEX(v) \
					v[0] *= mvScale[0]; v[1] *= mvScale[1]; v[2] *= mvScale[2];

				for(int i = 1; i < mvDim[0] - 2; i++) {
					for(int j = 1; j < mvDim[1] - 2; j++) {

						__GenerateQuad(pHeightMap, i, j, mvDim[0], vv[0], vv[1], vv[2], vv[3], vn[0], vn[1], vn[2], vn[3]);
						__SCALE_VERTEX(vv[0]); __SCALE_VERTEX(vv[1]); __SCALE_VERTEX(vv[2]); __SCALE_VERTEX(vv[3]);

						mrMesh->addTriangle(Triangle(vv[0], vv[1], vv[2]));
						mrMesh->addTriangle(Triangle(vv[0], vv[2], vv[3]));
						vNormals.push_back(Triangle(vn[0], vn[1], vn[2]));
						vNormals.push_back(Triangle(vn[0], vn[2], vn[3]));
					};
				};

				// Pre-scaled
				mvScale[0] = mvScale[1] = mvScale[2] = 1.0;

				// Create the VBO, passing vertex normals
				mrMesh->CreateVBO(&vNormals, NULL, true);
				//mrMesh->InsertTexture(this->mrTexture, 0);

				delete pHeightMap;
			};

			break;
	};
};


void TerrainHeightMap::Create(const tVector& vScale, const iVector2D& vDim, NonCopyBuffer<tScalar>::tRef& rHeightMap, tDataType dataType)
{
	Destroy();

	if(dataType == HTMAP_DEFAULT) dataType = GetDefaultType();

	mDataType = dataType;
	mvScale = vScale;
	mvDim = vDim;

	switch(dataType) {
		case HTMAP_TEXTURE:
			{
				PixelType::tRGBA32f::tRef rPixels = new PixelType::tRGBA32f(mvDim[0] * mvDim[1]);

				for(int i = 0; i < mvDim[0] * mvDim[1]; i++) {
					(*rPixels)[i] = __HeightToFloatPixel((*rHeightMap)[i]);
				};

				mrHeightMap = new Texture(getName());
				mrHeightMap->Create(rPixels, mvDim);
			};
			break;
		
		case HTMAP_FLOATV:
		case HTMAP_MESH: // TODO
			mrvfHeightMap = rHeightMap;

			break;
	};
};

void TerrainHeightMap::Destroy() {
	switch(mDataType) {
		case HTMAP_TEXTURE:
			// Destroy references
			mrHeightMap.Destroy();
			mrTerrainShader.Destroy();
			break;
		case HTMAP_FLOATV:
			mrShader.Destroy();
			mrvfHeightMap.Destroy();
			break;
		case HTMAP_MESH:
			mrShader.Destroy();
			mrMesh.Destroy();
			break;
	};
};

void TerrainHeightMap::render() {
	int i, j;

	// Need to translate from the centre, as we draw from the bottom left.
	//glTranslatef(-(float)mvDim[0] * mvScale[0] / 2, 0, -((float)mvDim[1] * mvScale[2]) / 2);

	if(mDataType == HTMAP_TEXTURE) {

		//
		// Draw heightmap with interpolation shader
		//

		
		mrTerrainShader->Activate();
		mrTerrainShader->setScale(mvScale);

		mrHeightMap->Bind(1);
		mrTexture->Bind(0);

		if(false) {
			// Draw one large quad, which is interpolated dynamically by the shader
			// UBER FAST!!
			
			glBegin(GL_QUADS);
			glVertex3d(0, 0, 0);
			glVertex3d(mvDim[0], 0, 0);
			glVertex3d(mvDim[0], 0, mvDim[1]);
			glVertex3d(0, 0, mvDim[1]);
			glEnd();
		} else {
			for(i = 0; i < mvDim[0]; i++) {
				for(j = 0; j < mvDim[1]; j++) {
					glBegin(GL_TRIANGLES);

					glTexCoord2f(i, j);
					glVertex3d(i, 0, j);
					glTexCoord2f(i + 1, j);
					glVertex3d(i + 1, 0, j);
					glTexCoord2f(i, j + 1);
					glVertex3d(i, 0, j + 1);

					glTexCoord2f(i + 1, j);
					glVertex3d(i + 1, 0, j);
					glTexCoord2f(i + 1, j + 1);
					glVertex3d(i + 1, 0, j + 1);
					glTexCoord2f(i, j + 1);
					glVertex3d(i, 0, j + 1);

					glEnd();
				};
			};
		};

		mrHeightMap->Release();
		mrTerrainShader->Deactivate();

	} else if (mDataType == HTMAP_MESH) {

		mrMesh->render();
		//mrMesh->setTexture(mrTexture);

	} else if (mDataType == HTMAP_FLOATV) {
		
		// Draw the heightmap vertex-by-vertex
		// SLOW!!! TODO: use VBOs.
		
		tVector v1, v2, v3, v4, n1, n2, n3, n4;
		glScalef(mvScale[0], mvScale[1], mvScale[2]);

		//glEnable(GL_LIGHTING);
		//glEnable(GL_BLEND);
		glBlendFunc(GL_DST_ALPHA,GL_ONE);
		

		if(!mrShader.isNull())
			mrShader->Activate();

		if(!mrTexture.isNull()) {
			mrTexture->Bind();
			glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		};

		
		for(i = 1; i < mvDim[0] - 2; i++) {
			for(j = 1; j < mvDim[1] - 2; j++) {
				
				__GenerateQuad(mrvfHeightMap->getData(), i, j, mvDim[0], v1, v2, v3, v4, n1, n2, n3, n4);

				glBegin(GL_TRIANGLES);
				
				glColor4f(0.1, 0.5, 0.6, 0.5);

				glNormal3fv(n1.toArray());
				glTexCoord2f(i, j);
				glVertex3fv(v1.toArray());

				glNormal3fv(n2.toArray());
				glTexCoord2f(i + 1, j);
				glVertex3fv(v2.toArray());

				glNormal3fv(n4.toArray());
				glTexCoord2f(i, j + 1);
				glVertex3fv(v4.toArray());

				glNormal3fv(n2.toArray());
				glTexCoord2f(i + 1, j);
				glVertex3fv(v2.toArray());

				glNormal3fv(n3.toArray());
				glTexCoord2f(i + 1, j + 1);
				glVertex3fv(v3.toArray());

				glNormal3fv(n4.toArray());
				glTexCoord2f(i, j + 1);
				glVertex3fv(v4.toArray());

				glEnd();
			};
		};

		if(!mrTexture.isNull())
			mrTexture->Release();
		if(!mrShader.isNull())
			mrShader->Deactivate();
	};
};

TerrainHeightMap::tDataType TerrainHeightMap::GetDefaultType() {
	return HTMAP_MESH; // Texture?o
};

WtfEngine::IDataObject * TerrainHeightMap::Load(const StringPool::Entry& sPath) {
	TerrainHeightMap * pMap = new TerrainHeightMap(sPath);
	WtfEngine::Image::tRef rImage = DataManager::GetInstance().Load<WtfEngine::Image>(String(sPath) + ".png");

	pMap->mrTexture = DataManager::GetInstance().Load<WtfEngine::TextureImage>(String(sPath) + "Tex.jpg");
	pMap->Create(tVector(1, 0.0001, 1), rImage, HTMAP_MESH);
	
	return pMap;
};


/**** Terrain Physical Models ****/

#ifdef __PHYSICS_BULLET__
#include <bullet/BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>

/**
 * Creates a physical model from a heightmap. This model should (obviously)
 * only be used for statics, and has an infinite mass to reflect this.
 **/
class TerrainHeightMapPhysicalModel : public WtfEngine::PhysicalModel {
	WtfEngine::NonCopyBuffer<tScalar>::tRef	mrHeightMap;

	static btCollisionShape * __CreateShape(const TerrainHeightMap::tRef& rHtMap) {
		btHeightfieldTerrainShape * pShape = new btHeightfieldTerrainShape(rHtMap->getDim()[0], rHtMap->getDim()[1],
			rHtMap->getDataFloatv()->getData(), 1000, 1, true, false);
		pShape->setLocalScaling(FROMVECT(rHtMap->getScale()));

		return pShape;
	};

public:
	TerrainHeightMapPhysicalModel(const TerrainHeightMap::tRef& rHtMap)
		: WtfEngine::PhysicalModel("", __CreateShape(rHtMap), 0),
		mrHeightMap(rHtMap->getDataFloatv()) {

	};

	DATA_LOADABLE(TerrainHeightMapPhysicalModel, GC_ADDEDSIZE_AUTO(WtfEngine::PhysicalModel, TerrainHeightMapPhysicalModel));
};

class TerrainMeshPhysicalModel : public WtfEngine::PhysicalModel {
	static btCollisionShape * __CreateShape(const TerrainHeightMap::tRef& rHtMap) {
		btTriangleMesh  * pShape = new btTriangleMesh();

		// This is the tacky way of doing things, duplicating meshes..
		Mesh::tRef rMesh = rHtMap->getMesh();
		for(int i = 0; i < rMesh->getNumTris(); i++) {
			pShape->addTriangle(FROMVECT(rMesh->getTriangles()[i][0]),
				FROMVECT(rMesh->getTriangles()[i][1]), FROMVECT(rMesh->getTriangles()[i][2]));
		};

		return new btBvhTriangleMeshShape(pShape, true);
	};

public:
	TerrainMeshPhysicalModel(const TerrainHeightMap::tRef& rHtMap)
		: WtfEngine::PhysicalModel("", __CreateShape(rHtMap), 0) {
	};

	DATA_LOADABLE(TerrainMeshPhysicalModel, GC_ADDEDSIZE_AUTO(WtfEngine::PhysicalModel, TerrainMeshPhysicalModel));
};

#endif

#include "pmodel.h"

PhysicalModel::tCacheRef TerrainHeightMap::getPhysicalModel() {
	switch(mDataType) {
		case HTMAP_MESH:
			return new TerrainMeshPhysicalModel(this);
		case HTMAP_FLOATV:
			return new TerrainHeightMapPhysicalModel(this);
		default:
			throw new std::logic_error("Height map is not initialized");
	};
};

void TerrainHeightMap::Test() {
	TerrainHeightMap::tRef rHtMap = reinterpret_cast<TerrainHeightMap *>(TerrainHeightMap::Load("media/images/heightmap"));
	(new Static(Static::tCreationOptions(), rHtMap, rHtMap->getPhysicalModel()))
		->setPosition(tVector(-50, -10, -50));
};
